#pragma once
#include<iostream>
#include<vector>
#include<semaphore.h>//信号量的头文件
#include<pthread.h>
//要通过信号量解决cp问题的--环形消费队列
//同时我们要学习信号量以及信号量的接口的使用

//环形队列我们使用动态数组通过取模运算控制下标来模拟

//信号量中的PV操作：P 和 V 操作是信号量操作，它们源自荷兰语 "Proberen"（测试）和 "Verhogen"（增加），分别对应于 "P" 操作（等待或减少）和 "V" 操作（信号或增加）。

//在环形队列中：生产者和消费者的下标相等的时候，分为两种情况，既可以是此时环形队列中数据为空，也可以是为满的时候

const static int defaultcap=5;

template<class T>
class RingQueue
{
private:
    void P(sem_t &sem)
    {
        sem_wait(&sem);//相当于P操作，用来减少信号量的值,当信号量的值为0时，会阻塞等待信号量的值恢复
    }
    void V(sem_t &sem)
    {
        sem_post(&sem);//post就是V操作，用来增加信号量
    }
    void Lock(pthread_mutex_t &lock)
    {
        pthread_mutex_lock(&lock);
    }
    void Unlock(pthread_mutex_t &lock)
    {
        pthread_mutex_unlock(&lock);
    }
public:
    RingQueue(int cap=defaultcap)
    :ringqueue_(cap),//这里是动态数组的默认构造的有参构造版本，可以只给一个数字初始化刚开始的容量大小
    cap_(cap),
    c_step_(0),
    p_step_(0)
    {
        sem_init(&cdata_sem_,0,0);//消费者信号量初始值为0，因为初始的时候没有可用数据
        /*sem_init接口介绍：
        int sem_init(sem_t *sem, int pshared, unsigned int value);
            sem：指向 sem_t 类型的信号量对象的指针。
            pshared：指示信号量是被进程内的线程共享（值为0），还是被进程之间共享（非零值）。
            value：指定信号量的初始值，这个值决定了允许同时访问信号量保护的资源的线程或进程的数量
        */
        sem_init(&pdata_sem_,0,cap);//生产者信号量,初始的时候所有的空间都可用，所以初始值为容量大小
        pthread_mutex_init(&c_lock_,nullptr);
        pthread_mutex_init(&p_lock_,nullptr);
    }
    //生产：
    void push(const T &in)
    {
        //生产操作要申请一个空余空间的信号量：
        P(pdata_sem_);//申请信号量的操作在加锁之前，因为有信号量大家就先抢到手，然后再慢慢竞争锁即可，增加了并发度
                      //如果放在锁里，要先申请锁之后再申请一个信号量，所有的操作都是串行执行，大大降低并发度

        //申请成功后：开始竞争生产者的锁：该锁保证了同时只会有一个生产者在增加数据到队列中
        Lock(p_lock_);

        //竞争到锁的生成者开始push数据：
        ringqueue_[p_step_]=in;//在生产者此时指向的下标处push数据

        //push后，生产者下标后移：
        p_step_++;
        p_step_%=cap_;//取模控制下标在数组中循环移动

        //解锁：
        Unlock(p_lock_);

        //V操作放在解锁外面即可，增加并发度，并且锁会导致线程串行运行，其中的代码量越少越好
        //锁保证访问和修改容器中数据的时候是安全的就足够了

        V(cdata_sem_);//此时增加一个了一个数据，所以让消费者的信号量+1

    }

    //消费：
    void pop(T*out)//输出型参数，要把获取到的任务输出给out指向的任务变量
    {
        //申请资源信号量：
        P(cdata_sem_);

        Lock(c_lock_);//申请消费者自己的锁
        *out=ringqueue_[c_step_];

        //移动消费者下标：
        c_step_++;
        c_step_%=cap_;

        //解锁：
        Unlock(c_lock_);

        //增加一个空闲空间信号量：
        V(pdata_sem_);

    }
    ~RingQueue()
    {
        //信号量使用完也要销毁
        sem_destroy(&cdata_sem_);
        sem_destroy(&pdata_sem_);
        pthread_mutex_destroy(&c_lock_);
        pthread_mutex_destroy(&p_lock_);
    }

private:
    vector<T>ringqueue_;//使用动态表模拟环形队列
    int cap_;

    int c_step_;//消费者下标
    int p_step_;//生产者下标

    sem_t cdata_sem_;//消费者关注的资源的信号量（也就是队列中的存有数据的空间个数）
    sem_t pdata_sem_;//生产者关注的资源的信号量（也就是队列中剩余空间个数）

    pthread_mutex_t c_lock_;//消费者使用的锁
    pthread_mutex_t p_lock_;//生产者使用的锁
};